package sistemadeproyectos;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.*;

/**
 * @author Giancarlo Nebiolo, 06-39965
 * @author Antonio Mundo, 06-39956
 * @autor Angel David, 06-40263
 * @version 1.0
 * 
 * Creada el 1 de febrero del 2012, hilo encargado de administrar
 * y controlar las conexiones de los programas cliente con el servidor de chat;
 * encargada de atender la solicitud de creacion o conexion a una sala de chat,
 * asi como de escuchar y difundir a los demas miembros de la sala de chat;
 * los mensajes del programa cliente al cual esta asociado.  
 */
public class AdminChat_thr extends Thread{
    /**Socket del cliente creado al aceptar la conexion por parte del servidor**/
    private Socket sock_cliente;
    
    /**Salida de datos asociada al socket del cliente**/
    private ObjectOutputStream salida;

    /**Entrada de datos asociada al socket del cliente**/
    private ObjectInputStream entrada;
    
    /** Tabla que contiene todas las listas de salas de chats existentes 
     * en el servidor y asociadas por nombre del equipo al que pertenecen
     **/
    private Hashtable<String, Hashtable<String, Chatroom>> listChatrooms;
    
    /** Lista que contiene los nombres de cada sala de chat asociada 
     * al equipo al que esta conectado el usuario asociado al socket de este hilo; 
     * asi como el nombre y la salida de datos del socket de cada 
     * usuario conectado a cada sala de chat del equipo
     **/
    private Hashtable<String, Chatroom> ChatsEquipo;
    
    /** Nombre de la sala de chat a la que se conecto el usuario asociado a este
     * hilo, esta variable se inicializa en el metodo "inicializarUsuario()"
     **/
    private String nombre_chatroom = "";
    
    /** Contiene los nombre y la salida de datos del socket de cada 
     * usuario conectado a la misma sala de chat al que esta conectado el usuario
     * al que esta asociado este hilo, esta variable se inicializa en el 
     * metodo "inicializarUsuario()"
     **/
    private Chatroom sesion = new Chatroom();
    
    /** Contiene el nombre que el usuario que esta conectado o asociado al socket
     * de este hilo
     * POR AHORA esta variable se inicializa en el metodo "inicializarUsuario()"
     **/
    private String nombre_usuario = "";
    
    /** Contiene el nombre del equipo al cual esta conectado el usuario asociado
     * al socket de este hilo.
     * POR AHORA esta variable se inicializa en el metodo "inicializarUsuario()"
     **/
    private String nombre_equipo = "";
    
    private boolean continuar = true;
    
    /**
     * Constructor de la clase AdminChat_thr
     * @param socket socket recibido del ServerSocket luego de aceptar una 
     * comunicacion con el cliente.
     * @param TablaChatrooms Donde se guardaran los nombres y las intancias 
     * de la clase Chatroom de cada sala de chat creada
     */
    public AdminChat_thr (Socket socket, Hashtable<String, Hashtable<String, 
            Chatroom>> TablaChatrooms) {
        this.sock_cliente = socket;
        this.listChatrooms = TablaChatrooms;
    }
    
    public void cerrar(){
        this.continuar = false;
        //desconectarUsuario2();        
    }
    
    
    /**
     * Se encarga de administrar la nueva conexion con un cliente;
     * encargada de mostrar las salas de chat disponibles al cliente y de atender
     * las posibles solicitudes validas de crear una nueva sala de chat o unirse 
     * a una ya existente. 
     * @exception IOException Arrojada si ocurre un problema de conexion con el
     * socket.
     * @exception ClassNotFoundException Arrojada si el cliente viola el 
     * protocolo de comunicacion, (no deberia pasar a menos que se establesca 
     * conexion con otro programa ajeno al cliente)
     * @return True si el usuaario se conecto de manera correcta a una sesion 
     * de chat, False si deseo no concretar la conexion
     */
    private boolean inicializarUsuario() throws IOException, 
            ClassNotFoundException 
    {
      boolean conexion = false;
      
      // Cuando se cierre el socket, esta opción hara que el cierre se
      // retarde automáticamente hasta 10 segundos dando tiempo al cliente
      // a leer los datos.
      (this.sock_cliente).setSoLinger(true, 10);
      
      this.salida = new ObjectOutputStream((this.sock_cliente).getOutputStream());
      this.entrada = new ObjectInputStream((this.sock_cliente).getInputStream());
           
      /*Leemos a que equipo pertenece el usuario*/
      this.nombre_equipo = (String)this.entrada.readObject();
           
      /* Obtenemos la lista de salas de chat asociadas al equipo al que esta 
       * conectado el usuario, si ese equipo no existe dentro de la lista total
       * lo agregamos asociandole una lista de salas de chat vacia*/
      this.ChatsEquipo = new Hashtable<String, Chatroom>();
      if ((this.listChatrooms).containsKey((this.nombre_equipo)))
      { (this.ChatsEquipo) = (this.listChatrooms).get((this.nombre_equipo)); }
      else { (this.listChatrooms).put((this.nombre_equipo), (this.ChatsEquipo)); }
                  
      /* Se obtiene la lista de Chatrooms.
       * OJO: este metodo no muestra chatrooms que haya creado otro 
       *      hilo (instancia de esta clase) despues de haber 
       *      ejecutado las dos instrucciones a continuacion.
       */
      ArrayList<String> sesiones_chat =  Collections.list((this.ChatsEquipo).keys());
      //El keys es un metodo sincronizable.
      
      //Se le manda la lista de chatrooms al cliente.
      salida.writeObject(sesiones_chat);

                            
      /* Se lee la opcion del usuario y el nombre del chatroom que
       * al que se va a conectar o va a crea. 
       * a) Se asume que el usuario ya esta logeado al sistema con un
       *      nombre de usuario unico.
       * b) Las elecciones que el cliente puede mandar solo pueden ser:
       *    "conectar": para crear o unirse a una sala de chat; 
       *    "desconectarse": para cancelar la conexion.
       */
      String eleccion = (String)entrada.readObject();
      if (eleccion.equals("conectar")) {
            this.nombre_chatroom = (String)entrada.readObject();
            this.nombre_usuario  = (String)entrada.readObject();
            
            
            /** Se verifica si el nombre de la sala de chat suministrado 
             * por el usuario existe dentro de la tabla de sesiones de chat, 
             * Si existe se une al usuario a esa sala de chat, si no existe se 
             * crea esa sala de chat. Se utiliza una declaracion "syncronized"
             * para evitar el caso extremo de que si una sala de chat "x" existe, 
             * con un usuario y este se desconecto al mismo tiempo que se 
             * otro usuario se conecto a esta sala, no haya la posibilidad de que se
             * elimine la sala de chat "x" mientras el usuario se una a ella
             */
            synchronized (this.ChatsEquipo) {
                if ((this.ChatsEquipo).containsKey(this.nombre_chatroom)) {
                    this.sesion = (this.ChatsEquipo).get(this.nombre_chatroom);
                    this.sesion.agregarUsuario(salida, (this.nombre_usuario));
                }                   
                else { 
                    this.sesion = new Chatroom(salida, (this.nombre_usuario));
                    (this.ChatsEquipo).put((this.nombre_chatroom), (this.sesion)); //El put es un metodo sincronizable
                }    
            }
            conexion =  true;
      }
      else { conexion = false; }
      return conexion;
    }
    
    /**
     * Cierra la conexion del socket asociado a esta clase,
     * terminando la comunicacion con el cliente y retirandolo de la sala de chat
     * a la cual estaba conectado; si el usuario era el unico en la sala de chat
     * elimina la sala de chat. Se utiliza la declaracion "syncronized"
     * para evitar el caso extremo de que si una sala de chat "x" existe, 
     * con un usuario y este se desconecto al mismo tiempo que otro desee acceder 
     * a la sala "x", no haya la posibilidad de que este nuevo usuario se una a 
     * "x" al mismo tiempo que se elimina.
     * @param sesion_actual instancia de la clase chatroom, clase que contiene
     * la lista de usuarios y de sockets asociados a una sala de chat.
     */
    private void desconectarUsuario() {  
        synchronized (this.ChatsEquipo){
            if ((this.sesion).unicoUsuario()) 
            { (this.ChatsEquipo).remove((this.nombre_chatroom)); }
        }
        
        /*Se indica al usuario que fue procesada su solicitud de desconexion*/
        try {
                salida.writeObject("desconectado");
                salida.writeObject(new ArrayList<String>());
                salida.flush();
        }
        catch(IOException E){}
        
        int hol = 0;
        try {
            hol = (this.sock_cliente).getSoLinger();
        }
        catch (Exception e){}
        
        /*Se elimina al usuario de la lista de Chat*/
        (this.sesion).eliminarUsuario(salida, nombre_usuario);     
        
        limpiar_socket();
    }
    
    
    private void desconectarUsuario2() {  
        synchronized (this.ChatsEquipo){
            if ((this.sesion).unicoUsuario()) 
            { (this.ChatsEquipo).remove((this.nombre_chatroom)); }
        }
        
        /*Se indica al usuario que fue procesada su solicitud de desconexion*/
        try {
                salida.writeObject("desconectado2");
                salida.writeObject(new ArrayList<String>());
                salida.flush();
        }
        catch(IOException E){}
        
        int hol = 0;
        try {
            hol = (this.sock_cliente).getSoLinger();
        }
        catch (Exception e){}
        
        /*Se elimina al usuario de la lista de Chat*/
        (this.sesion).eliminarUsuario(salida, nombre_usuario);     
        
        limpiar_socket();
    }
    
    
    
    
    
   /**
     * Cierra el socket; asi como la salida y entrada de datos del mismo.
     */
    private void limpiar_socket() {
        try { 
          entrada.close();
          salida.close();
          sock_cliente.close();
        } catch (Exception e) {}
    }
    
    /**Metodo que iniciara la ejecucion de este hilo. Su funcion sera la 
     * de procesar cada nuevo usuario, mostrandole la lista de salas de chat
     * actualmente creadas y agregandolo a la sala de chat que 
     * especifique, o terminando la conexion si asi o deseo el usuario;
     * una vez echo esto se encargara de mantenerse leyendo por mensajes de este
     * cliente y de retransmitirlos a los demas usuarios de la sala de chat**/
    public void run(){
        try {
            
            if (inicializarUsuario() != false) {
                String mensaje = "";
                while (this.continuar) {
                    mensaje = (String)(this.entrada).readObject();
  
                    if (!(mensaje.equals("desconectar"))) {
                        (this.sesion).enviarMensaje(mensaje, new ArrayList<String>());
                    }
                    else { 
                        /* Elimina al usuario de la sala de chat y confirma
                         * la desconexion*/
                        desconectarUsuario();
                        this.continuar = false; //Se para la ejecucion de este hilo
                    }
                }
            }
            else { limpiar_socket(); } 
        }
        catch (Exception e) { 
             if ((this.sesion).estaVacia()){
                 desconectarUsuario(); 
             }
             //System.out.println("Error en hilo chat");
             //e.printStackTrace(); 
        } 
    }
}
